using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;


namespace Asteroids
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    ///
 
    //There are three game states
    public enum GameState
    {
        StartScreen,    //start screen
        GameInProg,    //while the game is in progress
        GameOver       //Game Over
    }

    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Ship ship1;

        int numOfAsteroids = 1;
        float speedIncrease = 1;

        Bullet[] bullets = new Bullet[15];          // array of 15 bullet objects
        int bulletChoice = 0;                       // current bullet selected in bullets array
        float timeOfLastBullet = 0;                 // gameTime since last bullet was fired

        AlienShip[] alienships = new AlienShip[2];
        int numOfAliens = 0;
        int alienShipChoice = 0;
        int totalAlienShips = 0;

        AlienBullet[] alienBullets = new AlienBullet[20];
        int alienBulletChoice = 0;

        private Background background;

        public int Score = 0;
        bool[] BonusAvailable = new bool[5] { true, true, true, true, true };

        public int finalScore;
        Vector2 drawScore, drawHealth;              // position of the score and health
        public static GameState currentState;
        Texture2D startScreen, endScreen;
        SpriteFont Font1;
        public bool pauseKeyDown = false;
        public bool paused = false;

        SoundEffect GameOverSFX;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            //Add objects to Components list

            //background = new Background(this);
            //Components.Add(background);

            ship1 = new Ship(this, new Vector2(400, 250));
            Components.Add(ship1);

            for (int i = 0; i < 15; i++)                        // Initialize bullets
            {
                bullets[i] = new Bullet(this, new Vector2(-1000, -1000), 0, 0);
                bullets[i].collided();
            }

            for (int i = 0; i < 15; i++)
            {
                alienBullets[i] = new AlienBullet(this, new Vector2(-1000, -1000), new Vector2(-1000, -1000), 0);
                alienBullets[i].collided();
            }

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            startScreen = Content.Load<Texture2D>("startScreen");
            GameOverSFX = Content.Load<SoundEffect>("lose");
            endScreen = Content.Load<Texture2D>("gameOver");
            Font1 = Content.Load<SpriteFont>("Courier New");
            drawScore = new Vector2(150, 5);
            drawHealth = new Vector2(10, 5);
            background = new Background();
            Texture2D starbackground = Content.Load<Texture2D>("background");
            background.Load(GraphicsDevice, starbackground);

        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }


        // Reset the game: Called at end of game
        public void NewGame()
        {
            finalScore = Score;
            currentState = GameState.StartScreen;

            Components.Clear();
            Components.Add(ship1);
            ship1.Health = 100;
            Score = 0;
            numOfAsteroids = 1;
            numOfAliens = 0;
            totalAlienShips = 0;
            speedIncrease = 1;
        }

        // Restart the game: Called at beginning of new game
        public void RestartGame()
        {
            Components.Clear();

            finalScore = Score;         
            ship1 = new Ship(this, new Vector2(400, 250)); // Add player to game
            Components.Add(ship1);
            ship1.Health = 100;
            ship1.IsAlive = true;


            for (int i = 0; i < 5; i++)
            {
                BonusAvailable[i] = true;
            }

            numOfAliens = 0;
            totalAlienShips = 0;
            speedIncrease = 1;
            numOfAsteroids = 1;
            if (alienships[0] != null) alienships[0].IsAlive = false;
            if (alienships[1] != null) alienships[1].IsAlive = false;

            currentState = GameState.GameOver;
        }
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            float elapsedSeconds = (float)gameTime.ElapsedGameTime.TotalSeconds;      // Sec since last frame
            float totalTimeSeconds = (float)gameTime.TotalGameTime.TotalSeconds;      // Total running game time

            //-----------Button functions------------

            KeyboardState k = Keyboard.GetState();
            if (currentState == GameState.StartScreen || currentState == GameState.GameOver)
            {
                if (k.IsKeyDown(Keys.Enter))
                {
                    NewGame();
                }
                
                updateGameScreen();
            }

            //game is in progress
            else
            {
                checkPauseKey(k);
                if (!paused)
                {
                    if (!ship1.IsAlive)             //check if player is dead
                    {
                        GameOverSFX.Play();
                        RestartGame();              //if so go to game over
                        currentState = GameState.GameOver;
                    }

                    if (k.IsKeyDown(Keys.Left))          // Rotates left
                    {
                        if (ship1.IsAlive == true) ship1.Rotation -= 0.1f;
                    }
                    else if (k.IsKeyDown(Keys.Right))    // Rotates right
                    {
                        if (ship1.IsAlive == true) ship1.Rotation += 0.1f;
                    }

                    if (k.IsKeyDown(Keys.Up))            // Accelerates ship
                    {
                        if (ship1.IsAlive == true) ship1.moveFwd(elapsedSeconds);
                    }

                    if (k.IsKeyDown(Keys.Space))         // Fires bullet
                    {
                        if (ship1.IsAlive == true && totalTimeSeconds - timeOfLastBullet > 0.15)      // If more than .15sec has passed since last
                        //  bullet was fired, create a new one
                        {
                            timeOfLastBullet = totalTimeSeconds;
                            CreateBullet(totalTimeSeconds);
                        }
                    }

                    //-----------End of Button functions------------

                    //-----------Object Creation/Removal------------
                    RemoveBullets(totalTimeSeconds);
                    RemoveExplosions(totalTimeSeconds);

                    if (totalTimeSeconds % 20 < 0.015) speedIncrease += 0.2f;
                    CreateAsteroids(totalTimeSeconds);

                    CreateAlienShip(totalTimeSeconds);
                    CreateAlienBullets(totalTimeSeconds);
                    //-----------End of Object Creation/Removal------------

                    CollisionDetectionBullets(totalTimeSeconds);
                    CollisionDetectionShip(totalTimeSeconds);

                    ScoreBonuses();

                    background.Update(elapsedSeconds * 75);

                }
            }

            base.Update(gameTime);
        }

        public void updateGameScreen()
        {
            KeyboardState keyState = Keyboard.GetState();
            if (keyState.IsKeyDown(Keys.Enter))
            {
                if (currentState != GameState.GameInProg)
                    ship1.Position = new Vector2(400, 250);
                currentState = GameState.GameInProg;

            }
        }

        private void BeginPause(bool UserInitiated)
        {
            paused = true;
            PauseObjects();
            //TODO: Pause audio playback
            //TODO: Pause controller vibration
        }

        private void EndPause()
        {
            //TODO: Resume audio
            //TODO: Resume controller vibration
            paused = false;
            UnpauseObjects();
        }

        private void checkPauseKey(KeyboardState keyboardState)
        {
            bool pauseKeyDownThisFrame = (keyboardState.IsKeyDown(Keys.P));
            // If key was not down before, but is down now, we toggle the
            // pause setting
            if (!pauseKeyDown && pauseKeyDownThisFrame)
            {
                if (!paused)
                    BeginPause(true);
                else
                    EndPause();
            }
            pauseKeyDown = pauseKeyDownThisFrame;
        }

        /// <summary>*************************************************************************************************
        /// This is called when the game should draw itself.**********************************************************
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin();

            if (currentState != GameState.GameInProg)
            {
                if (currentState == GameState.StartScreen)
                {
                    spriteBatch.Draw(startScreen, Vector2.Zero, null, Color.White);
                    spriteBatch.End();
                    return;
                }
                else
                    //Draw end screen
                    spriteBatch.Draw(endScreen, Vector2.Zero, null, Color.White);
                    spriteBatch.DrawString(Font1, "Final Score: " + finalScore.ToString(), drawHealth, Color.White, 0, Vector2.Zero, 2, SpriteEffects.None, 0);
                    spriteBatch.End();
                    return;
            }

            
                background.Draw(spriteBatch);
                spriteBatch.DrawString(Font1, "Score: " + Score.ToString(), drawScore, Color.White);
                spriteBatch.DrawString(Font1, "Health: " + ship1.Health.ToString(), drawHealth, Color.White);
                
            spriteBatch.End();

            base.Draw(gameTime);
        }

        protected void CollisionDetectionBullets(float totalGameTime)
        // Handles collision detection between bullets and enemies
        {
            var removeList = new List<GameObject>();              // Objects to be removed
            var addList = new List<GameObject>();                 // Objects to be added

            foreach (var c in Components)
            {
                Bullet b = c as Bullet;
                if (b != null)              // If obj is a bullet
                {
                    foreach (var c2 in Components)
                    {
                        //Collisios between asteroids
                        Asteroid a = c2 as Asteroid;
                        if (a != null)              // If obj is an asteroid
                        {
                            if (a.boundingRectangle.Intersects(b.boundingRectangle))   // If asteroid and bullet are colliding
                            {
                                removeList.Add(a);                  // Add bullet and enemy to the removeList
                                removeList.Add(b);
                                a.collided();                       // And remove their boundingRectangles
                                b.collided();

                                if (!a.small)                       // If asteroid is large
                                {
                                    Score += 10;

                                    Random rand = new Random();
                                    Vector2 Velocity = a.Velocity + new Vector2(10, 10);
                                    Asteroid small1 = new Asteroid(this, a.Position, Velocity, true, speedIncrease);

                                    addList.Add(small1);

                                    Velocity = a.Velocity + new Vector2(-10, -10);
                                    Asteroid small2 = new Asteroid(this, a.Position, Velocity, true, speedIncrease);
                                    addList.Add(small2);

                                    AsteroidExplosion ae = new AsteroidExplosion(this, a.Position, totalGameTime, 1.0f);
                                    addList.Add(ae);                    // Creates new explosion and adds it to addList
                                }
                                else
                                {
                                    Score += 25;

                                    AsteroidExplosion ae = new AsteroidExplosion(this, a.Position, totalGameTime, 0.7f);
                                    addList.Add(ae);                    // Creates new explosion and adds it to addList
                                }
                            }
                        }
                        else
                        {
                            //Collisions between bullets and aliens
                            AlienShip aShip = c2 as AlienShip;
                            if (aShip != null)              // If obj is an alien
                            {
                                if (aShip.boundingRectangle.Intersects(b.boundingRectangle))  
                                {
                                    numOfAliens--;
                                    aShip.Dies();

                                    removeList.Add(aShip);                  // Add bullet and alien to the removeList
                                    removeList.Add(b);
                                    aShip.collided();                       // And remove their boundingRectangles
                                    b.collided();

                                    Score += 100;

                                    ShipExplosion se = new ShipExplosion(this, aShip.Position, totalGameTime, 0.8f, Color.Turquoise);
                                    addList.Add(se);                    // Creates new explosion and adds it to addList
                                }
                            }
                        }
                    }   
                }
            }

            foreach (var r in removeList)                         // Objects are removed from Components
            {
                Components.Remove(r);
            }
            
            foreach (var a in addList)                           // Objects are added to Components
            {
                Components.Add(a);
            }
        }

        protected void CollisionDetectionShip(float totalGameTime)
        // Collisions between ship and obstacles/enemies
        {
            var removeList = new List<GameObject>();              // Objects to be removed
            var addList = new List<GameObject>();                 // Objects to be added

            foreach (var c in Components)
            {
                Asteroid a = c as Asteroid;
                if (a != null)              // If obj is an asteroid
                {
                    if (a.boundingRectangle.Intersects(ship1.boundingRectangle))  
                    {
                        removeList.Add(a);                  // Add bullet to the removeList
                        a.collided();                       // And remove their boundingRectangles

                        ship1.HealthInjury(25);             //Decrease health by 25

                        if (ship1.IsAlive == false)         //Ship has been destroyed
                        {
                            ShipExplosion se = new ShipExplosion(this, ship1.Position, totalGameTime, 1.0f, Color.White);
                            addList.Add(se);                    // Creates new explosion and adds it to addList

                            removeList.Add(ship1);              // Remove ship and its bounding rectangle
                            ship1.collided();
                        }
                        else
                        {
                            // Adds smaller explosion
                            ShipExplosion se = new ShipExplosion(this, ship1.Position, totalGameTime, 0.6f, Color.White);
                            addList.Add(se);                    // Creates new explosion and adds it to addList
                        }
                    }
                }
                else
                {
                    AlienShip aShip = c as AlienShip;
                    if (aShip != null)              // If obj is an alien
                    {
                        if (aShip.boundingRectangle.Intersects(ship1.boundingRectangle))  
                        {
                            numOfAliens--;                          
                            aShip.Dies();                  

                            removeList.Add(aShip);                  // Add aliento the removeList
                            aShip.collided();                       // And remove boundingRectangles

                            ship1.HealthInjury(25);                 //Decrease health by 25

                            if (ship1.IsAlive == false)
                            {
                                ShipExplosion se = new ShipExplosion(this, ship1.Position, totalGameTime, 1.0f, Color.White);
                                addList.Add(se);                    // Creates new explosion and adds it to addList

                                removeList.Add(ship1);
                                ship1.collided();
                            }
                            else
                            {
                                // Adds smaller explosion
                                ShipExplosion se = new ShipExplosion(this, ship1.Position, totalGameTime, 0.6f, Color.White);
                                addList.Add(se);                    // Creates new explosion and adds it to addList
                            }
                        }
                    }
                    else
                    {
                        AlienBullet ab = c as AlienBullet;
                        if (ab != null)              // If obj is an alienbullet
                        {
                            if (ab.boundingRectangle.Intersects(ship1.boundingRectangle))   // If it intersects with an enemy
                            {
                                removeList.Add(ab);                  // Add alienbullet to the removeList
                                ab.collided();                       // And remove boundingRectangles

                                ship1.HealthInjury(10);         //if not dead decrease health by a fourth

                                if (ship1.IsAlive == false)
                                {
                                    ShipExplosion se = new ShipExplosion(this, ship1.Position, totalGameTime, 1.0f, Color.White);
                                    addList.Add(se);                    // Creates new explosion and adds it to addList

                                    removeList.Add(ship1);
                                    ship1.collided();
                                }
                                else
                                {
                                    // Adds smaller explosion
                                    ShipExplosion se = new ShipExplosion(this, ship1.Position, totalGameTime, 0.4f, Color.White);
                                    addList.Add(se);                    // Creates new explosion and adds it to addList
                                }
                            }
                        }
                    }
                }
            }

            foreach (var r in removeList)                         // Objects are removed from Components
            {
                Components.Remove(r);
            }

            foreach (var a in addList)                           // Objects are added to Components
            {
                Components.Add(a);
            }
        }

        protected void CreateAsteroids(float totalGameTime)
        {
            if (totalGameTime % 20 < 0.015)
            {
                numOfAsteroids++;
            };
            if (numOfAsteroids > 5) numOfAsteroids = 5;
            int xValue;
            int yValue;
            if (totalGameTime % 5 < 0.015)              // Every ~4 seconds
            {
                for (int i = 0; i < numOfAsteroids; i++)
                {
                    Random rand = new Random();
                    int side = rand.Next(0, 4);
                    if (side == 0)                      // Generates on left side
                    {
                        xValue = rand.Next(0, 75);
                        yValue = rand.Next(0, 550);
                    }
                    else if (side == 1)                 // Generates on top side
                    {
                        xValue = rand.Next(0, 800);
                        yValue = rand.Next(0, 75);
                    }
                    else if (side == 2)                 // Generates on right
                    {
                        xValue = rand.Next(725, 800);
                        yValue = rand.Next(0, 550);
                    }
                    else                                // Generates on bottom
                    {
                        xValue = rand.Next(0, 800);
                        yValue = rand.Next(475, 550);
                    }

                    Asteroid a = new Asteroid(this, 
                                              new Vector2(xValue, yValue), 
                                              new Vector2(rand.Next(-120, 120), rand.Next(-120, 120)), 
                                              false,
                                              speedIncrease);
 
                    Components.Add(a);
                }
            }
        }

        protected void CreateBullet(float totalGameTime)
        {
            bullets[bulletChoice] = new Bullet(this, ship1.Position, ship1.Rotation, totalGameTime);
            Components.Add(bullets[bulletChoice]);
            if (bulletChoice != 14) bulletChoice += 1;
            else bulletChoice = 0;
        }

        protected void CreateAlienShip(float totalGameTime)
        {
            if (totalGameTime % 20 < 0.015)
            {
                totalAlienShips++;
            };
            if (numOfAsteroids > 4) numOfAsteroids = 4;
            if (!(numOfAliens >= totalAlienShips))
            {
                int xValue;
                int yValue;

                if (totalGameTime % 10 < 0.015 && totalGameTime > 1)        //Every ~10 seconds
                {
                    Random rand = new Random();
                    int side = rand.Next(0, 2);
                    if (side == 0)                      // Generates on left side
                    {
                        xValue = rand.Next(0, 60);
                        yValue = rand.Next(0, 550);
                    }
                    else                                // Generates on right
                    {
                        xValue = rand.Next(740, 800);
                        yValue = rand.Next(0, 550);
                    }

                    alienships[alienShipChoice] = new AlienShip(this, new Vector2(xValue, yValue));
                    Components.Add(alienships[alienShipChoice]);
                    numOfAliens++;

                    if (alienShipChoice != 1) alienShipChoice++;
                    else alienShipChoice = 0;
                    
                }
            }
        }

        protected void CreateAlienBullets(float totalGameTime)
        {
            if (totalGameTime % 3 < 0.015)              //Every ~3 seconds
            {
                foreach (var aShip in alienships)
                {
                    if (aShip != null && aShip.IsAlive && aShip.Position.X > 0 && aShip.Position.Y > 0)
                    {
                        alienBullets[alienBulletChoice] = new AlienBullet(this, aShip.Position, ship1.Position, totalGameTime);
                        Components.Add(alienBullets[alienBulletChoice]);
                        if (alienBulletChoice != 19) alienBulletChoice += 1;
                        else alienBulletChoice = 0;
                    }
                }
            }
        }

        protected void RemoveBullets(float totalGameTime)
        {
            foreach (var b in bullets)
            {
                if (b != null)
                {
                    if (totalGameTime - b.timeAlive > 1.0)    // If bullet has been onscreen for > 1 sec
                    {
                        Components.Remove(b);                    //  remove it
                        b.collided();
                    }

                }
            }

            foreach (var ab in alienBullets)
            {
                if (ab != null)
                {
                    if (totalGameTime - ab.timeAlive > 1.5)    // If bullet has been onscreen for > 1.5 sec
                    {
                        Components.Remove(ab);                    //  remove it
                        ab.collided();
                    }

                }
            }
        }

        protected void RemoveExplosions(float totalGameTime)
        {
            var explosionRemoveList = new List<GameObject>();              // Explosions to be removed
            foreach (var c in Components)
            {
                // Removes asteroid explosions
                AsteroidExplosion ae = c as AsteroidExplosion;
                if (ae != null)
                {
                    if (totalGameTime - ae.timeAlive > 0.5)    // If bullet has been onscreen for > 1 sec
                    {
                        explosionRemoveList.Add(ae);                    //  remove it
                    }

                }
                else
                {
                    // Removes ship explosions
                    ShipExplosion se = c as ShipExplosion;
                    if (se != null)
                    {
                        if (totalGameTime - se.timeAlive > 0.5)    // If bullet has been onscreen for > 1 sec
                        {
                            explosionRemoveList.Add(se);                    //  remove it
                        }
                    }
                }
            }
            foreach (var r in explosionRemoveList)                // Objects are removed from Components
            {
                Components.Remove(r);
            }
        }

        protected void ScoreBonuses()
            // Adds health bonuses if player receives certain score
        {
            if (ship1.IsAlive)
            {
                if (BonusAvailable[0] && Score >= 500)
                {
                    BonusAvailable[0] = false;
                    ship1.Health += 25;
                }
                else if (BonusAvailable[1] && Score >= 1000)
                {
                    BonusAvailable[1] = false;
                    ship1.Health += 50;
                }
                else if (BonusAvailable[2] && Score > 2000)
                {
                    BonusAvailable[2] = false;
                    ship1.Health += 100;
                }
                else if (BonusAvailable[3] && Score >= 5000)
                {
                    BonusAvailable[3] = false;
                    ship1.Health += 200;
                }
                else if (BonusAvailable[4] && Score >= 10000)
                {
                    BonusAvailable[4] = false;
                    ship1.Health += 200;
                }
            }
        }

        protected void PauseObjects()
        {
            foreach (var c in Components)
            {
                GameObject gObject = c as GameObject;
                if (gObject != null)
                    gObject.Paused();
            }
        }

        protected void UnpauseObjects()
        {
            foreach (var c in Components)
            {
                GameObject gObject = c as GameObject;
                if (gObject != null)
                    gObject.UnPaused();
            }
        }
    }
}
